package config

import (
	"errors"
	"fmt"
	"gitee.com/sunny-zhangqiang/zcache/comm/fileio"
	"gopkg.in/yaml.v3"
	"os"
)

type MemConfig struct {
	ListNum           int
	OneListCacheItems int
	OneListCacheSize  int
}

type DiskConfig struct {
	FileName   string
	FileSizeMB int64
	Weight     int
}

type KickVolumeConfig struct {
	MaxErrors     int
	ErrTimeoutSec int
}

type CacheConfig struct {
	MemCache MemConfig
	SSDCache []*DiskConfig // SSD缓存介质列表, 可以为空
	HDDCache []*DiskConfig // HDD缓存介质列表, 不可为空

	MoveGoroutineNum      int  // Move协程数量
	VolumeNum             int  // 每个磁盘划分多少个分区
	FileAvgSizeKB         int  // 平均文件大小, 用于计算索引个数
	BlockSizeMB           int  // 块大小, 8MB的整数倍.
	IndexFlushDirtyNum    int  // 多少个脏数据时才将索引数据落盘
	IndexFlushIntervalSec int  // 多久尝试刷新一次索引
	IndexFlushStepSec     int  // 索引每一步刷新间隔
	MBlockPoolCapacity    int  // 每个volume持有多少个MBlock
	BlockRecycleThreshold int  // 空闲空间不足 2 * BlockRecycleThreshold时开始回收空间
	HotSpotRatio          int  // 热点对象百分比, 取值为0-50
	EnableTimerFlush      bool // 是否开启定期刷新
	TimerFlushIntervalSec int  // 定期刷新周期

	KickVolume KickVolumeConfig
}

func FixCacheConfig(cfg *CacheConfig) error {
	if cfg == nil {
		return errors.New("invalid cache config")
	}

	if cfg.SSDCache != nil {
		for _, v := range cfg.SSDCache {
			if v.FileName == "" {
				return errors.New("invalid cfg.SSDCache.Item.FileName")
			}

			if v.Weight == 0 {
				v.Weight = 10
			}
		}
	}

	if cfg.HDDCache == nil {
		return errors.New("invalid cfg.HDDCache")
	}

	for _, v := range cfg.HDDCache {
		if v.FileName == "" {
			return errors.New("invalid cfg.HDDCache.Item.FileName")
		}

		if v.Weight == 0 {
			v.Weight = 10
		}
	}

	if cfg.MoveGoroutineNum <= 0 {
		cfg.MoveGoroutineNum = 2
	}

	if cfg.VolumeNum <= 0 {
		cfg.VolumeNum = 1
	}

	if cfg.FileAvgSizeKB == 0 {
		return errors.New("please configure reasonable cfg.FileAvgSizeKB")
	}

	if cfg.FileAvgSizeKB < 4 {
		cfg.FileAvgSizeKB = 4
	}

	// default 60s
	if cfg.IndexFlushIntervalSec <= 0 {
		cfg.IndexFlushIntervalSec = 60
	}

	if cfg.IndexFlushStepSec <= 0 {
		cfg.IndexFlushStepSec = 1
	}

	if cfg.MBlockPoolCapacity <= 0 {
		cfg.MBlockPoolCapacity = 4
	}

	if cfg.BlockRecycleThreshold <= 0 {
		cfg.BlockRecycleThreshold = 8
	}

	if cfg.HotSpotRatio < 0 || cfg.HotSpotRatio > 50 {
		return errors.New("invalid cfg.HotSpotRatio, only values between 0-50 are allowed")
	}

	// default 60s
	if cfg.TimerFlushIntervalSec <= 0 {
		cfg.TimerFlushIntervalSec = 60
	}

	return nil
}

type Server struct {
	Listeners []string

	QuitTimeoutSec int64

	// Http server config
	ServerReadHeaderTimeoutMsec int
	ServerIdleTimeoutMsec       int
	ServerReadDataTimeoutMsec   int
	ServerWriteDataTimeoutMsec  int
	ServerMaxWorkers            int
	ServerMaxHeaderBytes        int

	// Http client config
	ClientConnTCPRecvBufKB          int
	ClientConnTimeoutMsec           int
	MaxClientIdleConns              int
	MaxClientIdleConnsPerHost       int
	ClientConnIdleTimeoutMsec       int
	TLSHandshakeTimeoutMsec         int
	ClientResponseHeaderTimeoutMsec int
	ClientReadDataTimeoutMsec       int
	ClientWriteDataTimeoutMsec      int

	EnableLog bool
	LogLevel  string
	LogPath   string // 必须设置绝对路径

	EnableAccessLog bool
	AccessLogPath   string // 必须设置绝对路径

	EnableForwardLog bool
	ForwardLogPath   string // 必须设置绝对路径

	Cache *CacheConfig

	IncludesDomains string // 必须设置绝对路径
}

func CheckAndFixServerConf(sc *Server) error {
	if sc == nil {
		return errors.New("invalid server config")
	}

	if sc.QuitTimeoutSec == 0 {
		sc.QuitTimeoutSec = 10
	}

	if sc.ServerReadHeaderTimeoutMsec == 0 {
		sc.ServerReadHeaderTimeoutMsec = 1000
	}

	if sc.ServerIdleTimeoutMsec == 0 {
		sc.ServerIdleTimeoutMsec = 60
	}

	if sc.ServerReadDataTimeoutMsec == 0 {
		sc.ServerReadDataTimeoutMsec = 1000
	}

	if sc.ServerWriteDataTimeoutMsec == 0 {
		sc.ServerWriteDataTimeoutMsec = 1000
	}

	if sc.ServerMaxWorkers == 0 {
		sc.ServerMaxWorkers = 10000
	}

	if sc.ServerMaxHeaderBytes == 0 {
		sc.ServerMaxHeaderBytes = 16 << 10 // 16K
	}

	if sc.ClientConnTimeoutMsec == 0 {
		sc.ClientConnTimeoutMsec = 3000
	}

	if sc.MaxClientIdleConns == 0 {
		sc.MaxClientIdleConns = 1024
	}

	if sc.MaxClientIdleConnsPerHost == 0 {
		sc.MaxClientIdleConnsPerHost = 128
	}

	if sc.ClientConnIdleTimeoutMsec == 0 {
		sc.ClientConnIdleTimeoutMsec = 30000
	}

	if sc.TLSHandshakeTimeoutMsec == 0 {
		sc.TLSHandshakeTimeoutMsec = 3000
	}

	if sc.ClientResponseHeaderTimeoutMsec == 0 {
		sc.ClientResponseHeaderTimeoutMsec = 5000
	}

	if sc.ClientReadDataTimeoutMsec == 0 {
		sc.ClientReadDataTimeoutMsec = 1000
	}

	if sc.ClientWriteDataTimeoutMsec == 0 {
		sc.ClientWriteDataTimeoutMsec = 1000
	}

	if sc.EnableLog {
		if sc.LogLevel != "debug" || sc.LogLevel != "warn" ||
			sc.LogLevel != "error" {
			return errors.New("invalid log level")
		}

		if sc.LogPath == "" || sc.LogPath[0] != '/' {
			return errors.New("log path must be an absolute path ")
		}
	}

	if sc.EnableAccessLog {
		if sc.AccessLogPath == "" || sc.AccessLogPath[0] != '/' {
			return errors.New("access log path must be an absolute path")
		}
	}

	if sc.EnableForwardLog {
		if sc.ForwardLogPath == "" || sc.ForwardLogPath[0] != '/' {
			return errors.New("forward log path must be an absolute path")
		}
	}

	err := FixCacheConfig(sc.Cache)
	if err != nil {
		return err
	}

	if sc.IncludesDomains == "" || sc.IncludesDomains[0] != '/' {
		return errors.New("includes domains path must be an absolute path")
	}

	if sc.IncludesDomains[len(sc.IncludesDomains)-1] != '/' {
		sc.IncludesDomains = sc.IncludesDomains + "/"
		if !fileio.IsDir(sc.IncludesDomains) {
			errors.New("includes domains path must be an absolute dir path")
		}
	}

	return nil
}

var ServerConf *Server

func LoadServerConf(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("load server conf failed, err: %v", err)
	}

	conf := &Server{}
	err = yaml.Unmarshal(data, conf)
	if err != nil {
		return fmt.Errorf("decode server conf failed, err: %v", err)
	}

	err = CheckAndFixServerConf(conf)
	if err != nil {
		return fmt.Errorf("check and fix server config failed, err: %v", err)
	}

	ServerConf = conf
	return nil
}
